package br.unicamp.mc715.bank;

import java.io.Serializable;

import br.unicamp.mc715.common.Clock;
import br.unicamp.mc715.transport.TransportId;

/**
 * Representa um {@link #operationType Saque/Depósito} com um determinado 
 * {@link #operationAmount valor} que deve ser executado em uma determinada {@link #operationAccount conta}.
 */
public class BankOperation implements Serializable, Comparable<BankOperation>
{
	/**Chatice para serialização*/
	private static final long serialVersionUID = 12350937586547927L;

	/**Momento em que a requisição foi enfileirada*/
	public long queueTime;
	/**Tipo da operação: 'd' para depositos, 'w' para saques e 'i' para operações canceladas.*/
	private char operationType = 0;
	/**ID da conta afetada*/
	private int operationAccount = 0;
	/**Quantidade de dinheiros sacadas/depositados*/
	private int operationAmount = 0;

	/**Identificador do cliente que enviou a requisicao*/
	public final TransportId clientId;
	/**Clock do servidor no momento do envio da requisicao*/
	private Clock clock;

	public Clock getClock() {
		return clock;
	}

	public void setClock(Clock clock) {
		this.clock = clock;
	}
	
	/** Instancia uma nova operação sem o clock, para tratar o caso do cliente.
	 * @param op Tipo da operação: : 'd' para depositos, 'w' para saques e 'i' para operações canceladas.
	 * @param valor Quantidade de dinheiro sacada/depositada nesta operação
	 * @param conta ID da conta afetada.
	 */
	public BankOperation(char op, int valor, int conta, TransportId clientId)
	{
		this.clientId = clientId;
		operationType = op;
		operationAccount = conta;
		this.operationAmount = valor;
	}
	
	/** Instancia uma nova operação sem o clock, para tratar o caso do cliente, sem valor para balanco.
	 * @param op Tipo da operação: : 'd' para depositos, 'w' para saques e 'i' para operações canceladas.
	 * @param valor Quantidade de dinheiro sacada/depositada nesta operação
	 * @param conta ID da conta afetada.
	 */
	public BankOperation(char op, int conta, TransportId clientId)
	{
		this.clientId = clientId;
		operationType = op;
		operationAccount = conta;
	}

	/** Instancia uma nova operação
	 * @param op Tipo da operação: : 'd' para depositos, 'w' para saques e 'i' para operações canceladas.
	 * @param valor Quantidade de dinheiro sacada/depositada nesta operação
	 * @param conta ID da conta afetada.
	 */
	public BankOperation(char op, int conta, TransportId clientId, Clock clock)
	{
		this.clientId = clientId;
		operationType = op;
		operationAccount = conta;
		this.clock = clock;
	}
	
	/** Instancia uma nova operação sem o clientId, para operacoes de ACK.
	 * @param op Tipo da operação: : 'd' para depositos, 'w' para saques e 'i' para operações canceladas.
	 * @param valor Quantidade de dinheiro sacada/depositada nesta operação
	 * @param conta ID da conta afetada.
	 */
	public BankOperation(char op, int conta, Clock clock)
	{
		clientId = null;
		operationType = op;
		operationAccount = conta;
		this.clock = clock;
	}

	/**
	 * Quantidade de dinheiro sacada/depositada nesta operação
	 * @return O valor desta operação.
	 * @see #operationAccount
	 */
	public int getOperationAccount() 
	{
		return operationAccount;
	}

	/** 
	 * Altera o tipo desta operação. 
	 * @param type O novo tipo desta operação: 'd' para depositos e 'w' para saques e 'i' para operações canceladas.
	 * @see #operationType 
	 */
	public void setOperationType(char type)
	{
		operationType = type;	
	}

	/** 
	 * Retorna o tipo desta operação. 
	 * @return type O novo tipo desta operação: 'd' para depositos e 'w' para saques e 'i' para operações canceladas.
	 * @see #operationType 
	 */
	public char getOperationType() 
	{
		return operationType;
	}

	/** 
	 * Retorna o valor desta operação (quantidade de $ sacada/depositada) 
	 * @return valor desta operação
	 * @see #operationAmount
	 */
	public int getOperationAmount() 
	{
		return operationAmount;
	}
	
	public TransportId getServerId()
	{
		return clock.getId();
	}

	/** 
	 * Retorna uma representação amigável desta operação. 
	 * @return Uma representação amigável desta operação.
	 */
	public String toString()
	{
		String op = clock.toString();

		if(operationType == 'd')
			op += " : Deposited";
		else if(operationType == 'w')
			op += " : Withdrawn";
		else if(operationType == 'b')
			op += " : Balance ";
		else
			op += " : Canceled";

		return op + " : " + operationAmount + "\n";
	}


	/**Uma mensagem de requisição do cliente para um dos servidores, encapsula um {@link BankOperation}*/
	public static class BankOperationRequest implements Serializable {
		private static final long serialVersionUID = 6747797616912592695L;

		/**Operação que está encapsulada*/
		public final BankOperation operation;

		/**Cria uma requisição para a operação especificada*/
		public BankOperationRequest(BankOperation operation) {
			this.operation = operation;
		}
	}
	/**Uma mensagem de resposta do servidor de volta para um cliente. Encapsula a {@link BankOperationRequest} original e o resultado da operação (Saldo ao fim da execução). */
	public static class BankOperationResponse implements Serializable {
		private static final long serialVersionUID = -7323101414874464325L;

		/**Requisição que está encapsulada*/
		public final BankOperation operation;
		/**Resultado da operação*/
		public final int result;

		/**Cria uma resposta para a requisição especificada*/
		public BankOperationResponse(BankOperation operation, int result) {
			this.operation = operation;
			this.result = result;
		}
	}
	
	/**Uma mensagem de acknowledge. Encapsula a {@link BankOperationRequest} original e o relogio local do remetente. */
	public static class BankOperationAck implements Serializable {
		private static final long serialVersionUID = -7323101414874464375L;

		/**Resultado da operação*/
		public final Clock clock;
		/**ID da conta afetada*/
		public final int operationAccount;
		
		/**Cria uma resposta para a requisição especificada*/
		public BankOperationAck(Clock clock, int account) {
			this.clock = clock;
			this.operationAccount = account;
		}
	}
	
	/**Uma mensagem de acknowledge. Encapsula a {@link BankOperationRequest} original e o relogio local do remetente. */
	public static class BankOperationEnquiry implements Serializable {
		private static final long serialVersionUID = -7323101414874464355L;

		/**Resultado da operação*/
		public final Clock clock;
		/**ID da conta afetada*/
		public final int operationAccount;
		/**ID do destinatario do enquiry*/
		public final TransportId destId;

		/**Cria uma resposta para a requisição especificada*/
		public BankOperationEnquiry(Clock clock, TransportId destId, int account) {
			this.clock = clock;
			this.operationAccount = account;
			this.destId = destId;
		}
	}

	public int compareTo(BankOperation o) {
		 return clock.compareTo(o.clock);
	}

}
